/*
 * Copyright 2018 datagear.tech. All Rights Reserved.
 */

package org.datagear.dbmodel;

import java.sql.Connection;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.datagear.dbinfo.ColumnInfo;
import org.datagear.dbinfo.DatabaseInfoResolver;
import org.datagear.dbinfo.DatabaseInfoResolverException;
import org.datagear.dbinfo.EntireTableInfo;
import org.datagear.dbinfo.ExportedKeyInfo;
import org.datagear.dbinfo.ImportedKeyInfo;
import org.datagear.dbinfo.ImportedKeyRule;
import org.datagear.dbinfo.TableInfo;
import org.datagear.model.Label;
import org.datagear.model.MapFeature;
import org.datagear.model.Model;
import org.datagear.model.ModelManager;
import org.datagear.model.Property;
import org.datagear.model.PropertyFeature;
import org.datagear.model.features.DescLabel;
import org.datagear.model.features.MaxLength;
import org.datagear.model.features.NameLabel;
import org.datagear.model.features.NotEditable;
import org.datagear.model.features.NotNull;
import org.datagear.model.features.NotReadable;
import org.datagear.model.features.Token;
import org.datagear.model.support.AbstractFeatured;
import org.datagear.model.support.DefaultDynamicBean;
import org.datagear.model.support.DefaultModel;
import org.datagear.model.support.DefaultModelManager;
import org.datagear.model.support.DynamicBeanProperty;
import org.datagear.model.support.MU;
import org.datagear.model.support.PrimitiveModelBuilder;
import org.datagear.persistence.PersistenceFeature;
import org.datagear.persistence.features.AutoGenerated;
import org.datagear.persistence.features.ColumnConverter;
import org.datagear.persistence.features.ColumnName;
import org.datagear.persistence.features.JdbcType;
import org.datagear.persistence.features.KeyRule;
import org.datagear.persistence.features.KeyRule.RuleType;
import org.datagear.persistence.features.MappedBy;
import org.datagear.persistence.features.ModelKeyColumnName;
import org.datagear.persistence.features.ModelKeyDeleteRule;
import org.datagear.persistence.features.ModelKeyPropertyName;
import org.datagear.persistence.features.ModelKeyUpdateRule;
import org.datagear.persistence.features.PointType;
import org.datagear.persistence.features.PropertyKeyColumnName;
import org.datagear.persistence.features.PropertyKeyDeleteRule;
import org.datagear.persistence.features.PropertyKeyPropertyName;
import org.datagear.persistence.features.PropertyKeyUpdateRule;
import org.datagear.persistence.features.RelationPoint;
import org.datagear.persistence.features.TableName;
import org.datagear.persistence.mapper.RelationMapper;
import org.datagear.persistence.mapper.RelationMapperResolver;
import org.springframework.core.convert.ConversionService;

/**
 * 抽象{@linkplain DevotedDatabaseModelResolver}。
 * <p>
 * 对于可能出现无法解析列模型的情况（比如列类型未知），可以通过设置此类的{@linkplain #unresolvableColumnHandleWay}字段来指定处理方式，
 * 如果设置为{@linkplain UnresolvableColumnHandleWay#PLACEHOLDER}（默认），此类将会使用{@linkplain #placeholderModel}（{@linkplain Model#getType()}为{@linkplain PlaceHolderType}）来代替，
 * 并为解析的属性设置{@linkplain NotEditable}和{@linkplain NotReadable}特性。此举可为界面交互类功能提供更友好的支持，比如可以看到哪些列不支持解析和处理。
 * </p>
 * <p>
 * 此类需要使用{@linkplain DatabaseInfoResolver}，因此
 * {@linkplain #resolve(Connection, ModelManager, ModelManager, String)}方法可能抛出
 * {@linkplain DatabaseInfoResolverException}。
 * </p>
 * 
 * @author datagear@163.com
 *
 */
public abstract class AbstractDevotedDatabaseModelResolver implements DevotedDatabaseModelResolver
{
	private DatabaseInfoResolver databaseInfoResolver;

	private PrimitiveModelResolver primitiveModelResolver;

	private ColumnConverterResolver columnConverterResolver;

	private ConversionService conversionService;

	private ModelNameResolver modelNameResolver = new IdentifierModelNameResolver();

	private PropertyNameResolver propertyNameResolver = new IdentifierPropertyNameResolver();

	private RelationMapperResolver relationMapperResolver = new RelationMapperResolver();

	private UnresolvableColumnHandleWay unresolvableColumnHandleWay = UnresolvableColumnHandleWay.PLACEHOLDER;

	private Model placeholderModel = new PrimitiveModelBuilder().build(PlaceHolderType.class);

	/** 默认作为Token属性的数目，靠前的这几个属性将默认添加Token特性 */
	private int defaultTokenPropertyCount = 3;

	public AbstractDevotedDatabaseModelResolver()
	{
		super();
	}

	public AbstractDevotedDatabaseModelResolver(DatabaseInfoResolver databaseInfoResolver,
			PrimitiveModelResolver primitiveModelResolver)
	{
		super();
		this.databaseInfoResolver = databaseInfoResolver;
		this.primitiveModelResolver = primitiveModelResolver;
	}

	public DatabaseInfoResolver getDatabaseInfoResolver()
	{
		return databaseInfoResolver;
	}

	public void setDatabaseInfoResolver(DatabaseInfoResolver databaseInfoResolver)
	{
		this.databaseInfoResolver = databaseInfoResolver;
	}

	public PrimitiveModelResolver getPrimitiveModelResolver()
	{
		return primitiveModelResolver;
	}

	public void setPrimitiveModelResolver(PrimitiveModelResolver primitiveModelResolver)
	{
		this.primitiveModelResolver = primitiveModelResolver;
	}

	public ColumnConverterResolver getColumnConverterResolver()
	{
		return columnConverterResolver;
	}

	public void setColumnConverterResolver(ColumnConverterResolver columnConverterResolver)
	{
		this.columnConverterResolver = columnConverterResolver;
	}

	public UnresolvableColumnHandleWay getUnresolvableColumnHandleWay()
	{
		return unresolvableColumnHandleWay;
	}

	public void setUnresolvableColumnHandleWay(UnresolvableColumnHandleWay unresolvableColumnHandleWay)
	{
		this.unresolvableColumnHandleWay = unresolvableColumnHandleWay;
	}

	public Model getPlaceholderModel()
	{
		return placeholderModel;
	}

	public void setPlaceholderModel(Model placeholderModel)
	{
		this.placeholderModel = placeholderModel;
	}

	public ConversionService getConversionService()
	{
		return conversionService;
	}

	public void setConversionService(ConversionService conversionService)
	{
		this.conversionService = conversionService;
	}

	public ModelNameResolver getModelNameResolver()
	{
		return modelNameResolver;
	}

	public void setModelNameResolver(ModelNameResolver modelNameResolver)
	{
		this.modelNameResolver = modelNameResolver;
	}

	public PropertyNameResolver getPropertyNameResolver()
	{
		return propertyNameResolver;
	}

	public void setPropertyNameResolver(PropertyNameResolver propertyNameResolver)
	{
		this.propertyNameResolver = propertyNameResolver;
	}

	public RelationMapperResolver getRelationMapperResolver()
	{
		return relationMapperResolver;
	}

	public void setRelationMapperResolver(RelationMapperResolver relationMapperResolver)
	{
		this.relationMapperResolver = relationMapperResolver;
	}

	public int getDefaultTokenPropertyCount()
	{
		return defaultTokenPropertyCount;
	}

	public void setDefaultTokenPropertyCount(int defaultTokenPropertyCount)
	{
		this.defaultTokenPropertyCount = defaultTokenPropertyCount;
	}

	@Override
	public Model resolve(Connection cn, ModelManager globalModelManager, ModelManager localModelManager, String table)
			throws DatabaseModelResolverException
	{
		String modelName = this.modelNameResolver.resolve(table);

		if (localModelManager == null)
			localModelManager = new DefaultModelManager();

		Model model = doResolve(cn, globalModelManager, localModelManager, table, modelName);

		Collection<Model> localModels = localModelManager.toCollection();

		for (Model localModel : localModels)
			resolveModelAndPropertyKeyPropertyNames(localModel);

		for (Model localModel : localModels)
			resolveModelRelationMappers(localModel);

		return model;
	}

	/**
	 * 执行解析。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param table
	 * @param modelName
	 * @return
	 * @throws DatabaseModelResolverException
	 */
	protected Model doResolve(Connection cn, ModelManager globalModelManager, ModelManager localModelManager,
			String table, String modelName) throws DatabaseModelResolverException
	{
		Model resolved = (globalModelManager == null ? null : globalModelManager.get(modelName));
		if (resolved != null)
			return resolved;
		resolved = (localModelManager == null ? null : localModelManager.get(modelName));
		if (resolved != null)
			return resolved;

		ModelBuilder modelBuilder = createModelBuilder();
		modelBuilder.init(modelName);

		// 必须在解析之前加入，放至后续Property解析因循环引用而导致死循环
		localModelManager.put(modelBuilder.getModel());

		EntireTableInfo entireTableInfo = resolveEntireTableInfo(cn, table);

		resolveModelType(cn, globalModelManager, localModelManager, modelBuilder, entireTableInfo);
		resolveModelProperties(cn, globalModelManager, localModelManager, modelBuilder, entireTableInfo);
		resolveModelFeatures(cn, globalModelManager, localModelManager, modelBuilder, entireTableInfo);

		modelBuilder.build();

		Model model = modelBuilder.getModel();

		return model;
	}

	/**
	 * 解析{@linkplain Model#getType()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param entireTableInfo
	 * @throws DatabaseModelResolverException
	 */
	protected void resolveModelType(Connection cn, ModelManager globalModelManager, ModelManager localModelManager,
			ModelBuilder modelBuilder, EntireTableInfo entireTableInfo) throws DatabaseModelResolverException
	{
		modelBuilder.setType(DefaultDynamicBean.class);
	}

	/**
	 * 解析{@linkplain Model#getProperties()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param entireTableInfo
	 * @throws DatabaseModelResolverException
	 */
	protected void resolveModelProperties(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, EntireTableInfo entireTableInfo)
			throws DatabaseModelResolverException
	{
		ColumnInfo[] columnInfos = entireTableInfo.getColumnInfos();

		if (columnInfos == null || columnInfos.length == 0)
			throw new DatabaseModelResolverException("No [" + ColumnInfo.class.getSimpleName()
					+ "] is defined in table [" + entireTableInfo.getTableInfo().getName() + "]");

		List<ImportedKeyInfo> resolvedImportedKeyInfos = new ArrayList<ImportedKeyInfo>();

		for (int i = 0; i < columnInfos.length; i++)
		{
			ColumnInfo columnInfo = columnInfos[i];

			Property property = null;

			ImportedKeyInfo importedKeyInfo = getImportedKeyInfoIf(entireTableInfo, columnInfo.getName());

			if (importedKeyInfo != null)
			{
				// 对于每一个外键列，下面会检测并且一起处理复合外键，所以这里需要校验是否已处理过
				if (!resolvedImportedKeyInfos.contains(importedKeyInfo))
				{
					List<ImportedKeyInfo> myImportedKeyInfos = findMyImportedKeyInfos(entireTableInfo, importedKeyInfo);

					property = resolveModelTableEntityProperty(cn, globalModelManager, localModelManager, modelBuilder,
							entireTableInfo, myImportedKeyInfos);

					resolvedImportedKeyInfos.addAll(myImportedKeyInfos);
				}
			}
			else
			{
				property = resolvePrimitiveProperty(cn, globalModelManager, localModelManager, modelBuilder,
						entireTableInfo, columnInfo, i);
			}

			if (property != null)
			{
				modelBuilder.addProperty(property);

				if (isPrimaryKeyColumn(entireTableInfo, columnInfo))
					modelBuilder.addIdProperty(property);

				int myUniqueIndex = getUniqueKeyIndexIf(entireTableInfo, columnInfo);
				if (myUniqueIndex >= 0)
					modelBuilder.addUniqueProperty(myUniqueIndex, property);
			}
		}

		resolveModelPropertiesForExportedKeys(cn, globalModelManager, localModelManager, modelBuilder, entireTableInfo);
	}

	/**
	 * 解析{@linkplain ExportedKeyInfo}可能的{@linkplain Model#getProperties()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param entireTableInfo
	 * @throws DatabaseModelResolverException
	 */
	protected void resolveModelPropertiesForExportedKeys(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, EntireTableInfo entireTableInfo)
			throws DatabaseModelResolverException
	{
		if (!entireTableInfo.hasExportedKey())
			return;

		ExportedKeyInfo[] exportedKeyInfos = entireTableInfo.getExportedKeyInfos();

		List<ExportedKeyInfo> resolvedExportedKeyInfos = new ArrayList<ExportedKeyInfo>();

		for (ExportedKeyInfo exportedKeyInfo : exportedKeyInfos)
		{
			// 对于每一个ExportedKeyInfo，下面会检测并且一起处理复合键，所以这里需要校验是否已处理过
			if (!resolvedExportedKeyInfos.contains(exportedKeyInfo))
			{
				List<ExportedKeyInfo> myExportedKeyInfos = findMyExportedKeyInfos(entireTableInfo, exportedKeyInfo);
				String fktableName = exportedKeyInfo.getFkTableName();

				String[] fktablePkNames = resolvePrimaryKeyColumnNames(cn, fktableName);

				// 如果目标表有主键，那么就不解析属性了，以减少解析载入量，因为太多的解析会使加载模型很慢
				if (fktablePkNames == null || fktablePkNames.length == 0)
				{
					EntireTableInfo fkEntireTableInfo = resolveEntireTableInfo(cn, fktableName);

					String joinTargetTable = getJoinTableTargetTableIf(entireTableInfo, fkEntireTableInfo);

					// fk表是关系表
					if (joinTargetTable != null)
					{
						EntireTableInfo propertyEntireTableInfo = resolveEntireTableInfo(cn, joinTargetTable);
						Property property = resolveJoinTableEntityProperty(cn, globalModelManager, localModelManager,
								modelBuilder, entireTableInfo, fkEntireTableInfo, propertyEntireTableInfo);

						if (property != null)
							modelBuilder.addProperty(property);
					}
					else
					{
						Property property = resolvePropertyTableProperty(cn, globalModelManager, localModelManager,
								modelBuilder, entireTableInfo, fkEntireTableInfo, myExportedKeyInfos);

						if (property != null)
							modelBuilder.addProperty(property);
					}
				}

				resolvedExportedKeyInfos.addAll(myExportedKeyInfos);
			}
		}
	}

	/**
	 * 解析表内的实体{@linkplain Property}。
	 * <p>
	 * 返回{@code null}，表示忽略此解析（为了最大化可用功能，忽略要比抛出异常好）。
	 * </p>
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param entireTableInfo
	 * @param importedKeyInfos
	 * @return
	 * @throws DatabaseModelResolverException
	 */
	protected Property resolveModelTableEntityProperty(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, EntireTableInfo entireTableInfo,
			List<ImportedKeyInfo> importedKeyInfos) throws DatabaseModelResolverException
	{
		PropertyBuilder propertyBuilder = createPropertyBuilder();

		resolveModelTableEntityPropertyName(cn, globalModelManager, localModelManager, modelBuilder, propertyBuilder,
				entireTableInfo, importedKeyInfos);
		resolveModelTableEntityPropertyModel(cn, globalModelManager, localModelManager, modelBuilder, propertyBuilder,
				entireTableInfo, importedKeyInfos);
		resolveModelTableEntityPropertyArray(cn, globalModelManager, localModelManager, modelBuilder, propertyBuilder,
				entireTableInfo, importedKeyInfos);
		resolveModelTableEntityPropertyCollectionType(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, importedKeyInfos);
		resolveModelTableEntityPropertyDefaultValue(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, importedKeyInfos);
		resolveModelTableEntityPropertyFeatures(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, importedKeyInfos);

		return propertyBuilder.build();
	}

	/**
	 * 解析表内实体{@linkplain Property}的{@linkplain Property#getName()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param importedKeyInfos
	 * @throws DatabaseModelResolverException
	 */
	protected void resolveModelTableEntityPropertyName(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, List<ImportedKeyInfo> importedKeyInfos)
			throws DatabaseModelResolverException
	{
		String propertyName0 = importedKeyInfos.get(0).getPkTableName();
		String propertyName1 = joinFkColumnNameForImportedKeyInfo(importedKeyInfos);

		String propertyName = this.propertyNameResolver.resolve(modelBuilder, propertyName0, propertyName1);

		propertyBuilder.setName(propertyName);
	}

	/**
	 * 解析表内实体{@linkplain Property}的{@linkplain Property#getModel()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param importedKeyInfos
	 * @throws DatabaseModelResolverException
	 */
	protected void resolveModelTableEntityPropertyModel(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, List<ImportedKeyInfo> importedKeyInfos)
			throws DatabaseModelResolverException
	{
		String ptable = importedKeyInfos.get(0).getPkTableName();

		String pmodelName = this.modelNameResolver.resolve(ptable);

		Model model = doResolve(cn, globalModelManager, localModelManager, ptable, pmodelName);

		propertyBuilder.setModel(model);
	}

	/**
	 * 解析表内实体{@linkplain Property}的{@linkplain Property#isArray()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param importedKeyInfos
	 * @throws DatabaseModelResolverException
	 */
	protected void resolveModelTableEntityPropertyArray(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, List<ImportedKeyInfo> importedKeyInfos)
			throws DatabaseModelResolverException
	{
		propertyBuilder.setArray(false);
	}

	/**
	 * 解析表内实体{@linkplain Property}的{@linkplain Property#getCollectionType()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param importedKeyInfos
	 * @throws DatabaseModelResolverException
	 */
	protected void resolveModelTableEntityPropertyCollectionType(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, List<ImportedKeyInfo> importedKeyInfos)
			throws DatabaseModelResolverException
	{
		propertyBuilder.setCollectionType(null);
	}

	/**
	 * 解析表内实体{@linkplain Property}的{@linkplain Property#getDefaultValue()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param importedKeyInfos
	 * @throws DatabaseModelResolverException
	 */
	protected void resolveModelTableEntityPropertyDefaultValue(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, List<ImportedKeyInfo> importedKeyInfos)
			throws DatabaseModelResolverException
	{
		propertyBuilder.setDefaultValue(null);
	}

	/**
	 * 解析表内实体{@linkplain Property}的{@linkplain Property#getFeatures()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param importedKeyInfos
	 * @throws DatabaseModelResolverException
	 */
	protected void resolveModelTableEntityPropertyFeatures(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, List<ImportedKeyInfo> importedKeyInfos)
			throws DatabaseModelResolverException
	{
		resolveModelTableEntityPropertyFeatureNameLabel(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, importedKeyInfos);
		resolveModelTableEntityPropertyFeatureDescLabel(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, importedKeyInfos);
		resolveModelTableEntityPropertyFeatureToken(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, importedKeyInfos);

		resolveModelTableEntityPropertyFeaturesPersistence(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, importedKeyInfos);
	}

	/**
	 * 解析表内实体{@linkplain Property}的持久化{@linkplain Property#getFeatures()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param importedKeyInfos
	 * @throws DatabaseModelResolverException
	 */
	protected void resolveModelTableEntityPropertyFeaturesPersistence(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, List<ImportedKeyInfo> importedKeyInfos)
			throws DatabaseModelResolverException
	{
		Property bdTarget = findBidirectionalTargetProperty(modelBuilder, propertyBuilder);
		if (isPersistencePropertyFeaturesResolved(bdTarget))
		{
			propertyBuilder.addFeature(MappedBy.class, new MappedBy(bdTarget.getName()));
		}
		else
		{
			resolveModelTableEntityPropertyFeaturePropertyKeyColumnName(cn, globalModelManager, localModelManager,
					modelBuilder, propertyBuilder, entireTableInfo, importedKeyInfos);
			resolveModelTableEntityPropertyFeatureModelKeyUpdateRule(cn, globalModelManager, localModelManager,
					modelBuilder, propertyBuilder, entireTableInfo, importedKeyInfos);
			resolveModelTableEntityPropertyFeatureModelKeyDeleteRule(cn, globalModelManager, localModelManager,
					modelBuilder, propertyBuilder, entireTableInfo, importedKeyInfos);
		}
	}

	/**
	 * 解析表内实体{@linkplain Property}的{@linkplain ModelKeyUpdateRule}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param importedKeyInfos
	 * @throws DatabaseModelResolverException
	 */
	protected void resolveModelTableEntityPropertyFeatureModelKeyUpdateRule(Connection cn,
			ModelManager globalModelManager, ModelManager localModelManager, ModelBuilder modelBuilder,
			PropertyBuilder propertyBuilder, EntireTableInfo entireTableInfo, List<ImportedKeyInfo> importedKeyInfos)
			throws DatabaseModelResolverException
	{
		ImportedKeyRule updateRule = null;

		for (ImportedKeyInfo importedKeyInfo : importedKeyInfos)
		{
			updateRule = importedKeyInfo.getUpdateRule();

			if (updateRule != null)
				break;
		}

		if (updateRule == null)
			return;

		ModelKeyUpdateRule modelKeyUpdateRule = new ModelKeyUpdateRule(
				KeyRule.valueOf(toKeyRuleType(updateRule), false));

		propertyBuilder.addFeature(ModelKeyUpdateRule.class, modelKeyUpdateRule);
	}

	/**
	 * 解析表内实体{@linkplain Property}的{@linkplain ModelKeyDeleteRule}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param importedKeyInfos
	 * @throws DatabaseModelResolverException
	 */
	protected void resolveModelTableEntityPropertyFeatureModelKeyDeleteRule(Connection cn,
			ModelManager globalModelManager, ModelManager localModelManager, ModelBuilder modelBuilder,
			PropertyBuilder propertyBuilder, EntireTableInfo entireTableInfo, List<ImportedKeyInfo> importedKeyInfos)
			throws DatabaseModelResolverException
	{
		ImportedKeyRule deleteRule = null;

		for (ImportedKeyInfo importedKeyInfo : importedKeyInfos)
		{
			deleteRule = importedKeyInfo.getDeleteRule();

			if (deleteRule != null)
				break;
		}

		if (deleteRule == null)
			return;

		ModelKeyDeleteRule modelKeyDeleteRule = new ModelKeyDeleteRule(
				KeyRule.valueOf(toKeyRuleType(deleteRule), false));

		propertyBuilder.addFeature(ModelKeyDeleteRule.class, modelKeyDeleteRule);
	}

	/**
	 * 解析表内实体{@linkplain Property}的{@linkplain PropertyKeyColumnName}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param importedKeyInfos
	 * @throws DatabaseModelResolverException
	 */
	protected void resolveModelTableEntityPropertyFeaturePropertyKeyColumnName(Connection cn,
			ModelManager globalModelManager, ModelManager localModelManager, ModelBuilder modelBuilder,
			PropertyBuilder propertyBuilder, EntireTableInfo entireTableInfo, List<ImportedKeyInfo> importedKeyInfos)
			throws DatabaseModelResolverException
	{
		PropertyKeyColumnName propertyKeyColumnName = new PropertyKeyColumnName(
				getFkColumnNamesForImportedKeyInfo(importedKeyInfos));

		propertyBuilder.addFeature(PropertyKeyColumnName.class, propertyKeyColumnName);

		PropertyTablePkColumnName propertyTablePkColumnName = new PropertyTablePkColumnName(
				getPkColumnNamesForImportedKeyInfo(importedKeyInfos));
		propertyBuilder.addFeature(PropertyTablePkColumnName.class, propertyTablePkColumnName);
	}

	/**
	 * 解析表内实体{@linkplain Property}的{@linkplain NameLabel}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param importedKeyInfos
	 * @throws DatabaseModelResolverException
	 */
	protected void resolveModelTableEntityPropertyFeatureNameLabel(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, List<ImportedKeyInfo> importedKeyInfos)
			throws DatabaseModelResolverException
	{
		String ptableName = importedKeyInfos.get(0).getPkTableName();
		addNameLabelFeature(propertyBuilder, new Label(ptableName));
	}

	/**
	 * 解析表内实体{@linkplain Property}的{@linkplain DescLabel}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param importedKeyInfos
	 * @throws DatabaseModelResolverException
	 */
	protected void resolveModelTableEntityPropertyFeatureDescLabel(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, List<ImportedKeyInfo> importedKeyInfos)
			throws DatabaseModelResolverException
	{
		String descLabelValue = null;

		Model propertyModel = propertyBuilder.getModel();
		DescLabel pmDescLabel = propertyModel.getFeature(DescLabel.class);
		if (pmDescLabel != null)
		{
			Label label = pmDescLabel.getValue();
			if (label != null)
				descLabelValue = label.getValue();
		}

		if (descLabelValue != null && !descLabelValue.isEmpty())
			addDescLabelFeature(propertyBuilder, new Label(descLabelValue));
	}

	/**
	 * 解析表内实体{@linkplain Property}的{@linkplain Token}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param importedKeyInfos
	 * @throws DatabaseModelResolverException
	 */
	protected void resolveModelTableEntityPropertyFeatureToken(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, List<ImportedKeyInfo> importedKeyInfos)
			throws DatabaseModelResolverException
	{
		if (isAsDefaultTokenProperty(modelBuilder, propertyBuilder))
			propertyBuilder.addFeature(Token.class);
	}

	/**
	 * 解析属性表{@linkplain Property}。
	 * <p>
	 * 返回{@code null}，表示忽略此解析（为了最大化可用功能，忽略要比抛出异常好）。
	 * </p>
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param entireTableInfo
	 * @param propertyEntireTableInfo
	 * @param exportedKeyInfos
	 * @return
	 * @throws DatabaseModelResolverException
	 */
	protected Property resolvePropertyTableProperty(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, EntireTableInfo entireTableInfo,
			EntireTableInfo propertyEntireTableInfo, List<ExportedKeyInfo> exportedKeyInfos)
			throws DatabaseModelResolverException
	{
		PropertyBuilder propertyBuilder = createPropertyBuilder();

		resolvePropertyTablePropertyName(cn, globalModelManager, localModelManager, modelBuilder, propertyBuilder,
				entireTableInfo, propertyEntireTableInfo, exportedKeyInfos);
		resolvePropertyTablePropertyModel(cn, globalModelManager, localModelManager, modelBuilder, propertyBuilder,
				entireTableInfo, propertyEntireTableInfo, exportedKeyInfos);
		resolvePropertyTablePropertyArray(cn, globalModelManager, localModelManager, modelBuilder, propertyBuilder,
				entireTableInfo, propertyEntireTableInfo, exportedKeyInfos);
		resolvePropertyTablePropertyCollectionType(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, propertyEntireTableInfo, exportedKeyInfos);
		resolvePropertyTablePropertyDefaultValue(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, propertyEntireTableInfo, exportedKeyInfos);
		resolvePropertyTablePropertyFeatures(cn, globalModelManager, localModelManager, modelBuilder, propertyBuilder,
				entireTableInfo, propertyEntireTableInfo, exportedKeyInfos);

		return propertyBuilder.build();
	}

	/**
	 * 解析属性表{@linkplain Property}的{@linkplain Property#getName()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param propertyEntireTableInfo
	 * @param exportedKeyInfos
	 * @throws DatabaseModelResolverException
	 */
	protected void resolvePropertyTablePropertyName(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo propertyEntireTableInfo,
			List<ExportedKeyInfo> exportedKeyInfos) throws DatabaseModelResolverException
	{
		String propertyName0 = propertyEntireTableInfo.getTableInfo().getName();
		String propertyName1 = propertyName0 + "_" + joinFkColumnNameForExportedKeyInfo(exportedKeyInfos);

		String propertyName = this.propertyNameResolver.resolve(modelBuilder, propertyName0, propertyName1);

		propertyBuilder.setName(propertyName);
	}

	/**
	 * 解析属性表{@linkplain Property}的{@linkplain Property#getModel()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param propertyEntireTableInfo
	 * @param exportedKeyInfos
	 * @throws DatabaseModelResolverException
	 */
	protected void resolvePropertyTablePropertyModel(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo propertyEntireTableInfo,
			List<ExportedKeyInfo> exportedKeyInfos) throws DatabaseModelResolverException
	{
		String ptable = propertyEntireTableInfo.getTableInfo().getName();

		String pmodelName = this.modelNameResolver.resolve(ptable);

		Model model = doResolve(cn, globalModelManager, localModelManager, ptable, pmodelName);

		propertyBuilder.setModel(model);
	}

	/**
	 * 解析属性表{@linkplain Property}的{@linkplain Property#isArray()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param propertyEntireTableInfo
	 * @param exportedKeyInfos
	 * @throws DatabaseModelResolverException
	 */
	protected void resolvePropertyTablePropertyArray(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo propertyEntireTableInfo,
			List<ExportedKeyInfo> exportedKeyInfos) throws DatabaseModelResolverException
	{
		propertyBuilder.setArray(false);
	}

	/**
	 * 解析属性表{@linkplain Property}的{@linkplain Property#getCollectionType()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param propertyEntireTableInfo
	 * @param exportedKeyInfos
	 * @throws DatabaseModelResolverException
	 */
	protected void resolvePropertyTablePropertyCollectionType(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo propertyEntireTableInfo,
			List<ExportedKeyInfo> exportedKeyInfos) throws DatabaseModelResolverException
	{
		boolean isCollection = true;

		String[] exportedKeyColumnNames = getFkColumnNamesForExportedKeyInfo(exportedKeyInfos);

		// 如果属性表内的外键是主键或者唯一键，则说明是单元属性

		if (equalsIgnoreOrder(exportedKeyColumnNames, propertyEntireTableInfo.getPrimaryKeyColumnNames()))
			isCollection = false;
		else
		{
			String[][] ptableUkColumnNames = propertyEntireTableInfo.getUniqueKeyColumnNames();

			if (ptableUkColumnNames != null)
			{
				for (String[] ptableUkColumnName : ptableUkColumnNames)
				{
					if (equalsIgnoreOrder(exportedKeyColumnNames, ptableUkColumnName))
					{
						isCollection = false;
						break;
					}
				}
			}
		}

		if (isCollection)
			propertyBuilder.setCollectionType(Collection.class);
	}

	/**
	 * 解析属性表{@linkplain Property}的{@linkplain Property#getDefaultValue()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param propertyEntireTableInfo
	 * @param exportedKeyInfos
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePropertyTablePropertyDefaultValue(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo propertyEntireTableInfo,
			List<ExportedKeyInfo> exportedKeyInfos) throws DatabaseModelResolverException
	{
		propertyBuilder.setDefaultValue(null);
	}

	/**
	 * 解析属性表{@linkplain Property}的{@linkplain Property#getFeatures()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param propertyEntireTableInfo
	 * @param exportedKeyInfos
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePropertyTablePropertyFeatures(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo propertyEntireTableInfo,
			List<ExportedKeyInfo> exportedKeyInfos) throws DatabaseModelResolverException
	{
		resolvePropertyTablePropertyFeatureNameLabel(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, propertyEntireTableInfo, exportedKeyInfos);
		resolvePropertyTablePropertyFeatureDescLabel(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, propertyEntireTableInfo, exportedKeyInfos);
		resolvePropertyTablePropertyFeatureToken(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, propertyEntireTableInfo, exportedKeyInfos);

		resolvePropertyTablePropertyFeaturesPersistence(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, propertyEntireTableInfo, exportedKeyInfos);
	}

	/**
	 * 解析属性表{@linkplain Property}的持久化{@linkplain Property#getFeatures()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param propertyEntireTableInfo
	 * @param exportedKeyInfos
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePropertyTablePropertyFeaturesPersistence(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo propertyEntireTableInfo,
			List<ExportedKeyInfo> exportedKeyInfos) throws DatabaseModelResolverException
	{
		Property bdTarget = findBidirectionalTargetProperty(modelBuilder, propertyBuilder);
		if (isPersistencePropertyFeaturesResolved(bdTarget))
		{
			propertyBuilder.addFeature(MappedBy.class, new MappedBy(bdTarget.getName()));
		}
		else
		{
			propertyBuilder.addFeature(RelationPoint.class, new RelationPoint(PointType.PROPERTY));
			propertyBuilder.addFeature(TableName.class,
					new TableName(propertyEntireTableInfo.getTableInfo().getName()));

			resolvePropertyTablePropertyFeatureModelKeyColumnName(cn, globalModelManager, localModelManager,
					modelBuilder, propertyBuilder, entireTableInfo, propertyEntireTableInfo, exportedKeyInfos);
			resolvePropertyTablePropertyFeaturePropertyKeyUpdateRule(cn, globalModelManager, localModelManager,
					modelBuilder, propertyBuilder, entireTableInfo, propertyEntireTableInfo, exportedKeyInfos);
			resolvePropertyTablePropertyFeaturePropertyKeyDeleteRule(cn, globalModelManager, localModelManager,
					modelBuilder, propertyBuilder, entireTableInfo, propertyEntireTableInfo, exportedKeyInfos);
		}
	}

	/**
	 * 解析属性表{@linkplain Property}的{@linkplain PropertyKeyUpdateRule}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param propertyEntireTableInfo
	 * @param exportedKeyInfos
	 * @throws DatabaseModelResolverException
	 */
	protected void resolvePropertyTablePropertyFeaturePropertyKeyUpdateRule(Connection cn,
			ModelManager globalModelManager, ModelManager localModelManager, ModelBuilder modelBuilder,
			PropertyBuilder propertyBuilder, EntireTableInfo entireTableInfo, EntireTableInfo propertyEntireTableInfo,
			List<ExportedKeyInfo> exportedKeyInfos) throws DatabaseModelResolverException
	{
		ImportedKeyRule updateRule = null;

		for (ExportedKeyInfo exportedKeyInfo : exportedKeyInfos)
		{
			updateRule = exportedKeyInfo.getUpdateRule();

			if (updateRule != null)
				break;
		}

		if (updateRule == null)
			return;

		PropertyKeyUpdateRule propertyKeyUpdateRule = new PropertyKeyUpdateRule(
				KeyRule.valueOf(toKeyRuleType(updateRule), false));

		propertyBuilder.addFeature(PropertyKeyUpdateRule.class, propertyKeyUpdateRule);
	}

	/**
	 * 解析属性表{@linkplain Property}的{@linkplain PropertyKeyDeleteRule}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param propertyEntireTableInfo
	 * @param exportedKeyInfos
	 * @throws DatabaseModelResolverException
	 */
	protected void resolvePropertyTablePropertyFeaturePropertyKeyDeleteRule(Connection cn,
			ModelManager globalModelManager, ModelManager localModelManager, ModelBuilder modelBuilder,
			PropertyBuilder propertyBuilder, EntireTableInfo entireTableInfo, EntireTableInfo propertyEntireTableInfo,
			List<ExportedKeyInfo> exportedKeyInfos) throws DatabaseModelResolverException
	{
		ImportedKeyRule deleteRule = null;

		for (ExportedKeyInfo exportedKeyInfo : exportedKeyInfos)
		{
			deleteRule = exportedKeyInfo.getDeleteRule();

			if (deleteRule != null)
				break;
		}

		if (deleteRule == null)
			return;

		PropertyKeyDeleteRule propertyKeyDeleteRule = new PropertyKeyDeleteRule(
				KeyRule.valueOf(toKeyRuleType(deleteRule), false));

		propertyBuilder.addFeature(PropertyKeyDeleteRule.class, propertyKeyDeleteRule);
	}

	/**
	 * 解析属性表{@linkplain Property}的{@linkplain ModelKeyColumnName}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param propertyEntireTableInfo
	 * @param exportedKeyInfos
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePropertyTablePropertyFeatureModelKeyColumnName(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo propertyEntireTableInfo,
			List<ExportedKeyInfo> exportedKeyInfos)
	{
		ModelKeyColumnName modelKeyColumnName = new ModelKeyColumnName(
				getFkColumnNamesForExportedKeyInfo(exportedKeyInfos));

		propertyBuilder.addFeature(ModelKeyColumnName.class, modelKeyColumnName);

		ModelTablePkColumnName modelTablePkColumnName = new ModelTablePkColumnName(
				getPkColumnNamesForExportedKeyInfo(exportedKeyInfos));
		propertyBuilder.addFeature(ModelTablePkColumnName.class, modelTablePkColumnName);
	}

	/**
	 * 解析属性表{@linkplain Property}的{@linkplain NameLabel}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param propertyEntireTableInfo
	 * @param exportedKeyInfos
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePropertyTablePropertyFeatureNameLabel(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo propertyEntireTableInfo,
			List<ExportedKeyInfo> exportedKeyInfos) throws DatabaseModelResolverException
	{
		TableInfo propertyTableInfo = propertyEntireTableInfo.getTableInfo();
		addNameLabelFeature(propertyBuilder, new Label(propertyTableInfo.getName()));
	}

	/**
	 * 解析属性表{@linkplain Property}的{@linkplain DescLabel}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param propertyEntireTableInfo
	 * @param exportedKeyInfos
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePropertyTablePropertyFeatureDescLabel(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo propertyEntireTableInfo,
			List<ExportedKeyInfo> exportedKeyInfos) throws DatabaseModelResolverException
	{
		TableInfo propertyTableInfo = propertyEntireTableInfo.getTableInfo();
		String comment = propertyTableInfo.getComment();

		if (comment != null && !comment.isEmpty())
			addDescLabelFeature(propertyBuilder, new Label(comment));
	}

	/**
	 * 解析属性表{@linkplain Property}的{@linkplain Token}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param propertyEntireTableInfo
	 * @param exportedKeyInfos
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePropertyTablePropertyFeatureToken(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo propertyEntireTableInfo,
			List<ExportedKeyInfo> exportedKeyInfos) throws DatabaseModelResolverException
	{
		// 集合属性不要Token
		if (propertyBuilder.isArray() || propertyBuilder.isCollection())
			return;

		if (isAsDefaultTokenProperty(modelBuilder, propertyBuilder))
			propertyBuilder.addFeature(Token.class);
	}

	/**
	 * 解析join表的实体{@linkplain Property}。
	 * <p>
	 * 返回{@code null}，表示忽略此解析（为了最大化可用功能，忽略要比抛出异常好）。
	 * </p>
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param entireTableInfo
	 * @param joinEntireTableInfo
	 * @param propertyEntireTableInfo
	 * @return
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected Property resolveJoinTableEntityProperty(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, EntireTableInfo entireTableInfo,
			EntireTableInfo joinEntireTableInfo, EntireTableInfo propertyEntireTableInfo)
			throws DatabaseModelResolverException
	{
		PropertyBuilder propertyBuilder = createPropertyBuilder();

		List<ImportedKeyInfo>[] importedKeyInfoLists = splitJoinTableImportedKeyInfos(entireTableInfo,
				joinEntireTableInfo, propertyEntireTableInfo);
		List<ImportedKeyInfo> modelImportedKeyInfos = importedKeyInfoLists[0];
		List<ImportedKeyInfo> propertyImportedKeyInfo = importedKeyInfoLists[1];

		resolveJoinTableEntityPropertyName(cn, globalModelManager, localModelManager, modelBuilder, propertyBuilder,
				entireTableInfo, joinEntireTableInfo, propertyEntireTableInfo, modelImportedKeyInfos,
				propertyImportedKeyInfo);
		resolveJoinTableEntityPropertyModel(cn, globalModelManager, localModelManager, modelBuilder, propertyBuilder,
				entireTableInfo, joinEntireTableInfo, propertyEntireTableInfo, modelImportedKeyInfos,
				propertyImportedKeyInfo);
		resolveJoinTableEntityPropertyArray(cn, globalModelManager, localModelManager, modelBuilder, propertyBuilder,
				entireTableInfo, joinEntireTableInfo, propertyEntireTableInfo, modelImportedKeyInfos,
				propertyImportedKeyInfo);
		resolveJoinTableEntityPropertyCollectionType(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, joinEntireTableInfo, propertyEntireTableInfo, modelImportedKeyInfos,
				propertyImportedKeyInfo);
		resolveJoinTableEntityPropertyDefaultValue(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, joinEntireTableInfo, propertyEntireTableInfo, modelImportedKeyInfos,
				propertyImportedKeyInfo);
		resolveJoinTableEntityPropertyFeatures(cn, globalModelManager, localModelManager, modelBuilder, propertyBuilder,
				entireTableInfo, joinEntireTableInfo, propertyEntireTableInfo, modelImportedKeyInfos,
				propertyImportedKeyInfo);

		return propertyBuilder.build();
	}

	/**
	 * 解析join表实体{@linkplain Property}的{@linkplain Property#getName()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param joinEntireTableInfo
	 * @param propertyEntireTableInfo
	 * @param modelImportedKeyInfos
	 * @param propertyImportedKeyInfos
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolveJoinTableEntityPropertyName(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo joinEntireTableInfo,
			EntireTableInfo propertyEntireTableInfo, List<ImportedKeyInfo> modelImportedKeyInfos,
			List<ImportedKeyInfo> propertyImportedKeyInfos) throws DatabaseModelResolverException
	{
		String propertyName0 = propertyEntireTableInfo.getTableInfo().getName();
		String propertyName1 = joinEntireTableInfo.getTableInfo().getName();

		String propertyName = this.propertyNameResolver.resolve(modelBuilder, propertyName0, propertyName1);

		propertyBuilder.setName(propertyName);
	}

	/**
	 * 解析join表实体{@linkplain Property}的{@linkplain Property#getModel()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param joinEntireTableInfo
	 * @param propertyEntireTableInfo
	 * @param modelImportedKeyInfos
	 * @param propertyImportedKeyInfos
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolveJoinTableEntityPropertyModel(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo joinEntireTableInfo,
			EntireTableInfo propertyEntireTableInfo, List<ImportedKeyInfo> modelImportedKeyInfos,
			List<ImportedKeyInfo> propertyImportedKeyInfos) throws DatabaseModelResolverException
	{
		String ptable = propertyEntireTableInfo.getTableInfo().getName();

		String pmodelName = this.modelNameResolver.resolve(ptable);

		Model model = doResolve(cn, globalModelManager, localModelManager, ptable, pmodelName);

		propertyBuilder.setModel(model);
	}

	/**
	 * 解析join表实体{@linkplain Property}的{@linkplain Property#isArray()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param joinEntireTableInfo
	 * @param propertyEntireTableInfo
	 * @param modelImportedKeyInfos
	 * @param propertyImportedKeyInfos
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolveJoinTableEntityPropertyArray(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo joinEntireTableInfo,
			EntireTableInfo propertyEntireTableInfo, List<ImportedKeyInfo> modelImportedKeyInfos,
			List<ImportedKeyInfo> propertyImportedKeyInfos) throws DatabaseModelResolverException
	{
		propertyBuilder.setArray(false);
	}

	/**
	 * 解析join表实体{@linkplain Property}的{@linkplain Property#getCollectionType()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param joinEntireTableInfo
	 * @param propertyEntireTableInfo
	 * @param modelImportedKeyInfos
	 * @param propertyImportedKeyInfos
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolveJoinTableEntityPropertyCollectionType(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo joinEntireTableInfo,
			EntireTableInfo propertyEntireTableInfo, List<ImportedKeyInfo> modelImportedKeyInfos,
			List<ImportedKeyInfo> propertyImportedKeyInfos) throws DatabaseModelResolverException
	{
		boolean isCollection = true;

		String[] jtableMfkColumnName = getFkColumnNamesForImportedKeyInfo(modelImportedKeyInfos);

		// 如果join表内的模型端外键是主键或者唯一键，则说明是单元属性；否则，是多元属性

		if (equalsIgnoreOrder(jtableMfkColumnName, joinEntireTableInfo.getPrimaryKeyColumnNames()))
			isCollection = false;
		else
		{
			String[][] jtableUkColumnNames = joinEntireTableInfo.getUniqueKeyColumnNames();

			if (jtableUkColumnNames != null)
			{
				for (String[] jtableUkColumnName : jtableUkColumnNames)
				{
					if (equalsIgnoreOrder(jtableMfkColumnName, jtableUkColumnName))
					{
						isCollection = false;
						break;
					}
				}
			}
		}

		if (isCollection)
			propertyBuilder.setCollectionType(Collection.class);
	}

	/**
	 * 解析join表实体{@linkplain Property}的{@linkplain Property#getDefaultValue()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param joinEntireTableInfo
	 * @param propertyEntireTableInfo
	 * @param modelImportedKeyInfos
	 * @param propertyImportedKeyInfos
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolveJoinTableEntityPropertyDefaultValue(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo joinEntireTableInfo,
			EntireTableInfo propertyEntireTableInfo, List<ImportedKeyInfo> modelImportedKeyInfos,
			List<ImportedKeyInfo> propertyImportedKeyInfos) throws DatabaseModelResolverException
	{
		propertyBuilder.setDefaultValue(null);
	}

	/**
	 * 解析join表实体{@linkplain Property}的{@linkplain Property#getFeatures()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param joinEntireTableInfo
	 * @param propertyEntireTableInfo
	 * @param modelImportedKeyInfos
	 * @param propertyImportedKeyInfos
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolveJoinTableEntityPropertyFeatures(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo joinEntireTableInfo,
			EntireTableInfo propertyEntireTableInfo, List<ImportedKeyInfo> modelImportedKeyInfos,
			List<ImportedKeyInfo> propertyImportedKeyInfos) throws DatabaseModelResolverException
	{
		resolveJoinTableEntityPropertyFeatureNameLabel(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, joinEntireTableInfo, propertyEntireTableInfo, modelImportedKeyInfos,
				propertyImportedKeyInfos);
		resolveJoinTableEntityPropertyFeatureDescLabel(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, joinEntireTableInfo, propertyEntireTableInfo, modelImportedKeyInfos,
				propertyImportedKeyInfos);
		resolveJoinTableEntityPropertyFeatureToken(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, joinEntireTableInfo, propertyEntireTableInfo, modelImportedKeyInfos,
				propertyImportedKeyInfos);

		resolveJoinTableEntityPropertyFeaturesPersistence(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, joinEntireTableInfo, propertyEntireTableInfo, modelImportedKeyInfos,
				propertyImportedKeyInfos);
	}

	/**
	 * 解析join表实体{@linkplain Property}的持久化{@linkplain Property#getFeatures()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param joinEntireTableInfo
	 * @param propertyEntireTableInfo
	 * @param modelImportedKeyInfos
	 * @param propertyImportedKeyInfos
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolveJoinTableEntityPropertyFeaturesPersistence(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo joinEntireTableInfo,
			EntireTableInfo propertyEntireTableInfo, List<ImportedKeyInfo> modelImportedKeyInfos,
			List<ImportedKeyInfo> propertyImportedKeyInfos) throws DatabaseModelResolverException
	{
		Property bdTarget = findBidirectionalTargetProperty(modelBuilder, propertyBuilder);
		if (isPersistencePropertyFeaturesResolved(bdTarget))
		{
			propertyBuilder.addFeature(MappedBy.class, new MappedBy(bdTarget.getName()));
		}
		else
		{
			propertyBuilder.addFeature(RelationPoint.class, new RelationPoint(PointType.JOIN));
			propertyBuilder.addFeature(TableName.class, new TableName(joinEntireTableInfo.getTableInfo().getName()));

			resolveJoinTableEntityPropertyFeatureModelKeyColumnName(cn, globalModelManager, localModelManager,
					modelBuilder, propertyBuilder, entireTableInfo, joinEntireTableInfo, propertyEntireTableInfo,
					modelImportedKeyInfos, propertyImportedKeyInfos);

			resolveJoinTableEntityPropertyFeaturePropertyKeyColumnName(cn, globalModelManager, localModelManager,
					modelBuilder, propertyBuilder, entireTableInfo, joinEntireTableInfo, propertyEntireTableInfo,
					modelImportedKeyInfos, propertyImportedKeyInfos);
		}
	}

	/**
	 * 解析join表实体{@linkplain Property}的{@linkplain ModelKeyColumnName}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param joinEntireTableInfo
	 * @param propertyEntireTableInfo
	 * @param modelImportedKeyInfos
	 * @param propertyImportedKeyInfos
	 */
	protected void resolveJoinTableEntityPropertyFeatureModelKeyColumnName(Connection cn,
			ModelManager globalModelManager, ModelManager localModelManager, ModelBuilder modelBuilder,
			PropertyBuilder propertyBuilder, EntireTableInfo entireTableInfo, EntireTableInfo joinEntireTableInfo,
			EntireTableInfo propertyEntireTableInfo, List<ImportedKeyInfo> modelImportedKeyInfos,
			List<ImportedKeyInfo> propertyImportedKeyInfos)
	{
		ModelKeyColumnName modelKeyColumnName = new ModelKeyColumnName(
				getFkColumnNamesForImportedKeyInfo(modelImportedKeyInfos));

		propertyBuilder.addFeature(ModelKeyColumnName.class, modelKeyColumnName);

		ModelTablePkColumnName modelTablePkColumnName = new ModelTablePkColumnName(
				getPkColumnNamesForImportedKeyInfo(modelImportedKeyInfos));
		propertyBuilder.addFeature(ModelTablePkColumnName.class, modelTablePkColumnName);
	}

	/**
	 * 解析join表实体{@linkplain Property}的{@linkplain PropertyKeyColumnName}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param joinEntireTableInfo
	 * @param propertyEntireTableInfo
	 * @param modelImportedKeyInfos
	 * @param propertyImportedKeyInfos
	 */
	protected void resolveJoinTableEntityPropertyFeaturePropertyKeyColumnName(Connection cn,
			ModelManager globalModelManager, ModelManager localModelManager, ModelBuilder modelBuilder,
			PropertyBuilder propertyBuilder, EntireTableInfo entireTableInfo, EntireTableInfo joinEntireTableInfo,
			EntireTableInfo propertyEntireTableInfo, List<ImportedKeyInfo> modelImportedKeyInfos,
			List<ImportedKeyInfo> propertyImportedKeyInfos)
	{
		PropertyKeyColumnName propertyKeyColumnName = new PropertyKeyColumnName(
				getFkColumnNamesForImportedKeyInfo(propertyImportedKeyInfos));

		propertyBuilder.addFeature(PropertyKeyColumnName.class, propertyKeyColumnName);

		PropertyTablePkColumnName propertyTablePkColumnName = new PropertyTablePkColumnName(
				getPkColumnNamesForImportedKeyInfo(propertyImportedKeyInfos));
		propertyBuilder.addFeature(PropertyTablePkColumnName.class, propertyTablePkColumnName);
	}

	/**
	 * 解析join表实体{@linkplain Property}的{@linkplain NameLabel}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param joinEntireTableInfo
	 * @param propertyEntireTableInfo
	 * @param modelImportedKeyInfos
	 * @param propertyImportedKeyInfos
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolveJoinTableEntityPropertyFeatureNameLabel(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo joinEntireTableInfo,
			EntireTableInfo propertyEntireTableInfo, List<ImportedKeyInfo> modelImportedKeyInfos,
			List<ImportedKeyInfo> propertyImportedKeyInfos) throws DatabaseModelResolverException
	{
		addNameLabelFeature(propertyBuilder, new Label(propertyEntireTableInfo.getTableInfo().getName()));
	}

	/**
	 * 解析join表实体{@linkplain Property}的{@linkplain DescLabel}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param joinEntireTableInfo
	 * @param propertyEntireTableInfo
	 * @param modelImportedKeyInfos
	 * @param propertyImportedKeyInfos
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolveJoinTableEntityPropertyFeatureDescLabel(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo joinEntireTableInfo,
			EntireTableInfo propertyEntireTableInfo, List<ImportedKeyInfo> modelImportedKeyInfos,
			List<ImportedKeyInfo> propertyImportedKeyInfos) throws DatabaseModelResolverException
	{
		String comment = propertyEntireTableInfo.getTableInfo().getComment();

		if (comment != null && !comment.isEmpty())
			addDescLabelFeature(propertyBuilder, new Label(comment));
	}

	/**
	 * 解析join表实体{@linkplain Property}的{@linkplain Token}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param joinEntireTableInfo
	 * @param propertyEntireTableInfo
	 * @param modelImportedKeyInfos
	 * @param propertyImportedKeyInfos
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolveJoinTableEntityPropertyFeatureToken(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, EntireTableInfo joinEntireTableInfo,
			EntireTableInfo propertyEntireTableInfo, List<ImportedKeyInfo> modelImportedKeyInfos,
			List<ImportedKeyInfo> propertyImportedKeyInfos) throws DatabaseModelResolverException
	{
		// 集合属性不要Token
		if (propertyBuilder.isArray() || propertyBuilder.isCollection())
			return;

		if (isAsDefaultTokenProperty(modelBuilder, propertyBuilder))
			propertyBuilder.addFeature(Token.class);
	}

	/**
	 * 解析基本{@linkplain Property}。
	 * <p>
	 * 返回{@code null}，表示忽略此解析（为了最大化可用功能，忽略要比抛出异常好）。
	 * </p>
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param entireTableInfo
	 * @param columnInfo
	 * @param columnIndex
	 * @return
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected Property resolvePrimitiveProperty(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, EntireTableInfo entireTableInfo,
			ColumnInfo columnInfo, int columnIndex) throws DatabaseModelResolverException
	{
		PropertyBuilder propertyBuilder = createPropertyBuilder();

		resolvePrimitivePropertyName(cn, globalModelManager, localModelManager, modelBuilder, propertyBuilder,
				entireTableInfo, columnInfo, columnIndex);
		resolvePrimitivePropertyModel(cn, globalModelManager, localModelManager, modelBuilder, propertyBuilder,
				entireTableInfo, columnInfo, columnIndex);

		if (propertyBuilder.getModel() == null)
		{
			if (UnresolvableColumnHandleWay.IGNORE.equals(this.unresolvableColumnHandleWay))
			{
				return null;
			}
			else if (UnresolvableColumnHandleWay.PLACEHOLDER.equals(this.unresolvableColumnHandleWay))
			{
				propertyBuilder.setModel(this.placeholderModel);

				propertyBuilder.addFeature(NotEditable.class);
				propertyBuilder.addFeature(NotReadable.class);
			}
			else
				throw new UnsupportedColumnModelResolvingException(columnInfo);
		}

		resolvePrimitivePropertyArray(cn, globalModelManager, localModelManager, modelBuilder, propertyBuilder,
				entireTableInfo, columnInfo, columnIndex);
		resolvePrimitivePropertyCollectionType(cn, globalModelManager, localModelManager, modelBuilder, propertyBuilder,
				entireTableInfo, columnInfo, columnIndex);
		resolvePrimitivePropertyDefaultValue(cn, globalModelManager, localModelManager, modelBuilder, propertyBuilder,
				entireTableInfo, columnInfo, columnIndex);
		resolvePrimitivePropertyFeatures(cn, globalModelManager, localModelManager, modelBuilder, propertyBuilder,
				entireTableInfo, columnInfo, columnIndex);

		return propertyBuilder.build();
	}

	/**
	 * 解析基本{@linkplain Property}的{@linkplain Property#getName()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param columnInfo
	 * @param columnIndex
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePrimitivePropertyName(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, ColumnInfo columnInfo, int columnIndex)
			throws DatabaseModelResolverException
	{
		String propertyName = this.propertyNameResolver.resolve(modelBuilder, columnInfo.getName());

		propertyBuilder.setName(propertyName);
	}

	/**
	 * 解析基本{@linkplain Property}的{@linkplain Property#getModel()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param columnInfo
	 * @param columnIndex
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePrimitivePropertyModel(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, ColumnInfo columnInfo, int columnIndex)
			throws DatabaseModelResolverException
	{
		Model model = this.primitiveModelResolver.resolve(cn, entireTableInfo, columnInfo);

		propertyBuilder.setModel(model);
	}

	/**
	 * 解析基本{@linkplain Property}的{@linkplain Property#isArray()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param columnInfo
	 * @param columnIndex
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePrimitivePropertyArray(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, ColumnInfo columnInfo, int columnIndex)
			throws DatabaseModelResolverException
	{
		propertyBuilder.setArray(false);
	}

	/**
	 * 解析基本{@linkplain Property}的{@linkplain Property#getCollectionType()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param columnInfo
	 * @param columnIndex
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePrimitivePropertyCollectionType(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, ColumnInfo columnInfo, int columnIndex)
			throws DatabaseModelResolverException
	{
		propertyBuilder.setCollectionType(null);
	}

	/**
	 * 解析基本{@linkplain Property}的{@linkplain Property#getDefaultValue()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param columnInfo
	 * @param columnIndex
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePrimitivePropertyDefaultValue(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, ColumnInfo columnInfo, int columnIndex)
			throws DatabaseModelResolverException
	{
		Object defaultValue = null;

		String strDftValue = columnInfo.getDefaultValue();

		if (this.conversionService != null && strDftValue != null && !strDftValue.isEmpty())
		{
			try
			{
				defaultValue = this.conversionService.convert(strDftValue, propertyBuilder.getModel().getType());
			}
			catch (Exception e)
			{
			}
		}

		if (defaultValue instanceof String)
		{
			String dvStr = (String) defaultValue;
			int length = dvStr.length();

			// 去除开头和结尾的引号
			if (length > 1)
			{
				if ((dvStr.startsWith("\"") && dvStr.endsWith("\"")) || (dvStr.startsWith("'") && dvStr.endsWith("'")))
					dvStr = dvStr.substring(1, length - 1);
			}

			defaultValue = dvStr;
		}

		propertyBuilder.setDefaultValue(defaultValue);
	}

	/**
	 * 解析基本{@linkplain Property}的{@linkplain Property#getFeatures()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param columnInfo
	 * @param columnIndex
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePrimitivePropertyFeatures(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, ColumnInfo columnInfo, int columnIndex)
			throws DatabaseModelResolverException
	{
		resolvePrimitivePropertyFeatureColumnName(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, columnInfo, columnIndex);
		resolvePrimitivePropertyFeatureJdbcType(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, columnInfo, columnIndex);
		resolvePrimitivePropertyFeatureMaxLength(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, columnInfo, columnIndex);
		resolvePrimitivePropertyFeatureAutoGenerated(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, columnInfo, columnIndex);
		resolvePrimitivePropertyFeatureDescLabel(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, columnInfo, columnIndex);
		resolvePrimitivePropertyFeatureToken(cn, globalModelManager, localModelManager, modelBuilder, propertyBuilder,
				entireTableInfo, columnInfo, columnIndex);
		resolvePrimitivePropertyFeatureColumnConverter(cn, globalModelManager, localModelManager, modelBuilder,
				propertyBuilder, entireTableInfo, columnInfo, columnIndex);
		resolvePrimitivePropertyFeatureNotNull(cn, globalModelManager, localModelManager, modelBuilder, propertyBuilder,
				entireTableInfo, columnInfo, columnIndex);
	}

	/**
	 * 解析基本{@linkplain Property}的{@linkplain ColumnName}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param columnInfo
	 * @param columnIndex
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePrimitivePropertyFeatureColumnName(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, ColumnInfo columnInfo, int columnIndex)
			throws DatabaseModelResolverException
	{
		propertyBuilder.addFeature(ColumnName.class, new ColumnName(columnInfo.getName()));
	}

	/**
	 * 解析基本{@linkplain Property}的{@linkplain JdbcType}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param columnInfo
	 * @param columnIndex
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePrimitivePropertyFeatureJdbcType(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, ColumnInfo columnInfo, int columnIndex)
			throws DatabaseModelResolverException
	{
		propertyBuilder.addFeature(JdbcType.class, new JdbcType(columnInfo.getType()));
	}

	/**
	 * 解析基本{@linkplain Property}的{@linkplain MaxLength}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param columnInfo
	 * @param columnIndex
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePrimitivePropertyFeatureMaxLength(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, ColumnInfo columnInfo, int columnIndex)
			throws DatabaseModelResolverException
	{
		int columnSize = columnInfo.getSize();

		if (columnSize > 0)
			propertyBuilder.addFeature(MaxLength.class, new MaxLength(columnSize));
	}

	/**
	 * 解析基本{@linkplain Property}的{@linkplain AutoGenerated}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param columnInfo
	 * @param columnIndex
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePrimitivePropertyFeatureAutoGenerated(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, ColumnInfo columnInfo, int columnIndex)
			throws DatabaseModelResolverException
	{
		if (columnInfo.isAutoincrement())
			propertyBuilder.addFeature(AutoGenerated.class);
	}

	/**
	 * 解析基本{@linkplain Property}的{@linkplain DescLabel}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param columnInfo
	 * @param columnIndex
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePrimitivePropertyFeatureDescLabel(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, ColumnInfo columnInfo, int columnIndex)
			throws DatabaseModelResolverException
	{
		String comment = columnInfo.getComment();

		if (comment != null && !comment.isEmpty())
			addDescLabelFeature(propertyBuilder, new Label(comment));
	}

	/**
	 * 解析基本{@linkplain Property}的{@linkplain Token}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param columnInfo
	 * @param columnIndex
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePrimitivePropertyFeatureToken(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, ColumnInfo columnInfo, int columnIndex)
			throws DatabaseModelResolverException
	{
		if (isAsDefaultTokenProperty(modelBuilder, propertyBuilder))
			propertyBuilder.addFeature(Token.class);
	}

	/**
	 * 解析基本{@linkplain Property}的{@linkplain ColumnConverter}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param columnInfo
	 * @param columnIndex
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePrimitivePropertyFeatureColumnConverter(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, ColumnInfo columnInfo, int columnIndex)
			throws DatabaseModelResolverException
	{
		ColumnConverter columnConverter = (this.columnConverterResolver == null ? null
				: this.columnConverterResolver.resolve(cn, entireTableInfo, columnInfo));

		if (columnConverter != null)
			propertyBuilder.addFeature(ColumnConverter.class, columnConverter);
	}

	/**
	 * 解析基本{@linkplain Property}的{@linkplain NotNull}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @param entireTableInfo
	 * @param columnInfo
	 * @param columnIndex
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolvePrimitivePropertyFeatureNotNull(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, PropertyBuilder propertyBuilder,
			EntireTableInfo entireTableInfo, ColumnInfo columnInfo, int columnIndex)
			throws DatabaseModelResolverException
	{
		if (!columnInfo.isNullable())
			propertyBuilder.addFeature(NotNull.class);
	}

	/**
	 * 解析{@linkplain Model#getFeatures()}。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param entireTableInfo
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolveModelFeatures(Connection cn, ModelManager globalModelManager, ModelManager localModelManager,
			ModelBuilder modelBuilder, EntireTableInfo entireTableInfo) throws DatabaseModelResolverException
	{
		resolveModelFeatureTableName(cn, globalModelManager, localModelManager, modelBuilder, entireTableInfo);
		resolveModelFeatureDescLabel(cn, globalModelManager, localModelManager, modelBuilder, entireTableInfo);
	}

	/**
	 * 解析{@linkplain Model}的{@linkplain TableName}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param entireTableInfo
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolveModelFeatureTableName(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, EntireTableInfo entireTableInfo)
			throws DatabaseModelResolverException
	{
		modelBuilder.addFeature(TableName.class, new TableName(entireTableInfo.getTableInfo().getName()));
	}

	/**
	 * 解析{@linkplain Model}的{@linkplain DescLabel}特性。
	 * 
	 * @param cn
	 * @param globalModelManager
	 * @param localModelManager
	 * @param modelBuilder
	 * @param entireTableInfo
	 * @throws DatabaseModelResolverException
	 * 
	 */
	protected void resolveModelFeatureDescLabel(Connection cn, ModelManager globalModelManager,
			ModelManager localModelManager, ModelBuilder modelBuilder, EntireTableInfo entireTableInfo)
			throws DatabaseModelResolverException
	{
		String comment = entireTableInfo.getTableInfo().getComment();

		if (comment != null && !comment.isEmpty())
			addDescLabelFeature(modelBuilder, new Label(comment));
	}

	/**
	 * 判断给定{@linkplain ColumnInfo}是否是主键。
	 * 
	 * @param entireTableInfo
	 * @param columnInfo
	 * @return
	 */
	protected boolean isPrimaryKeyColumn(EntireTableInfo entireTableInfo, ColumnInfo columnInfo)
	{
		if (!entireTableInfo.hasPrimaryKey())
			return false;

		String[] primaryKeyColumnNames = entireTableInfo.getPrimaryKeyColumnNames();

		String myColumnName = columnInfo.getName();

		for (String primaryKeyColumnName : primaryKeyColumnNames)
		{
			if (primaryKeyColumnName.equals(myColumnName))
				return true;
		}

		return false;
	}

	/**
	 * 如果{@linkplain ColumnInfo}是唯一键，则返回其在{@linkplain EntireTableInfo#getUniqueKeyColumnNames()}数组中的索引，否则返回{@code -1}。
	 * 
	 * @param entireTableInfo
	 * @param columnInfo
	 * @return
	 */
	protected int getUniqueKeyIndexIf(EntireTableInfo entireTableInfo, ColumnInfo columnInfo)
	{
		if (!entireTableInfo.hasUniqueKey())
			return -1;

		String columnName = columnInfo.getName();

		String[][] uniqueKeyColumnNames = entireTableInfo.getUniqueKeyColumnNames();

		for (int i = 0; i < uniqueKeyColumnNames.length; i++)
		{
			String[] uniqueKeyColumnName = uniqueKeyColumnNames[i];

			for (String uk : uniqueKeyColumnName)
			{
				if (columnName.equals(uk))
					return i;
			}
		}

		return -1;
	}

	/**
	 * 判断属性是否可作为默认Token。
	 * 
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @return
	 */
	protected boolean isAsDefaultTokenProperty(ModelBuilder modelBuilder, PropertyBuilder propertyBuilder)
	{
		List<Property> properties = modelBuilder.getProperties();

		int myIndex = (properties == null ? 0 : properties.size());

		if (myIndex < this.defaultTokenPropertyCount)
			return true;
		else
			return false;
	}

	/**
	 * 解析表主键列名数组。
	 * 
	 * @param cn
	 * @param table
	 * @return
	 * @throws DatabaseInfoResolverException
	 */
	protected String[] resolvePrimaryKeyColumnNames(Connection cn, String table) throws DatabaseInfoResolverException
	{
		return this.databaseInfoResolver.getPrimaryKeyColumnNames(cn, table);
	}

	/**
	 * 解析{@linkplain EntireTableInfo}。
	 * 
	 * @param cn
	 * @param table
	 * @return
	 * @throws DatabaseInfoResolverException
	 */
	protected EntireTableInfo resolveEntireTableInfo(Connection cn, String table) throws DatabaseInfoResolverException
	{
		return this.databaseInfoResolver.getEntireTableInfo(cn, table);
	}

	/**
	 * 解析{@linkplain TableInfo}。
	 * 
	 * @param cn
	 * @param table
	 * @return
	 * @throws DatabaseInfoResolverException
	 */
	protected TableInfo resolveTableInfo(Connection cn, String table) throws DatabaseInfoResolverException
	{
		TableInfo tableInfo = this.databaseInfoResolver.getTableInfo(cn, table);

		return tableInfo;
	}

	/**
	 * 如果表是关系表，则返回关系目标表，否则，返回{@code null}。
	 * <p>
	 * 关系表：仅存储一个表和另一个表的ID字段，没有其他额外的字段。
	 * </p>
	 * 
	 * @param sourceEntireTableInfo
	 * @param joinEntireTableInfo
	 * @return
	 */
	protected String getJoinTableTargetTableIf(EntireTableInfo sourceEntireTableInfo,
			EntireTableInfo joinEntireTableInfo)
	{
		if (!joinEntireTableInfo.hasImportedKey())
			return null;

		ColumnInfo[] columnInfos = joinEntireTableInfo.getColumnInfos();
		ImportedKeyInfo[] importedKeyInfos = joinEntireTableInfo.getImportedKeyInfos();

		if (columnInfos.length != importedKeyInfos.length)
			return null;

		Set<String> tables = new HashSet<String>();

		for (ImportedKeyInfo importedKeyInfo : importedKeyInfos)
		{
			tables.add(importedKeyInfo.getPkTableName());
		}

		if (tables.size() != 2)
			return null;

		String[] tableArray = tables.toArray(new String[2]);
		String sourceTableName = sourceEntireTableInfo.getTableInfo().getName();

		if (sourceTableName.equals(tableArray[0]))
			return tableArray[1];
		else if (sourceTableName.equals(tableArray[1]))
			return tableArray[0];
		else
			return null;
	}

	/**
	 * 拆分join表的{@linkplain ImportedKeyInfo}。
	 * <p>
	 * 返回数组的第一个元素对应{@code modelEntireTableInfo}的外键，第二个元素对应
	 * {@code propertyEntireTableInfo}的外键。
	 * </p>
	 * 
	 * @param modelEntireTableInfo
	 * @param joinEntireTableInfo
	 * @param propertyEntireTableInfo
	 * @return
	 */
	protected List<ImportedKeyInfo>[] splitJoinTableImportedKeyInfos(EntireTableInfo modelEntireTableInfo,
			EntireTableInfo joinEntireTableInfo, EntireTableInfo propertyEntireTableInfo)
	{
		@SuppressWarnings("unchecked")
		List<ImportedKeyInfo>[] lists = new List[2];

		String modelTableName = modelEntireTableInfo.getTableInfo().getName();
		String propertyTableName = propertyEntireTableInfo.getTableInfo().getName();
		ImportedKeyInfo[] importedKeyInfos = joinEntireTableInfo.getImportedKeyInfos();

		List<ImportedKeyInfo> list0 = new ArrayList<ImportedKeyInfo>();
		String[] mpks = modelEntireTableInfo.getPrimaryKeyColumnNames();
		for (String mpk : mpks)
		{
			for (ImportedKeyInfo importedKeyInfo : importedKeyInfos)
			{
				if (modelTableName.equals(importedKeyInfo.getPkTableName())
						&& mpk.equals(importedKeyInfo.getPkColumnName()))
					list0.add(importedKeyInfo);
			}
		}

		List<ImportedKeyInfo> list1 = new ArrayList<ImportedKeyInfo>();
		String[] ppks = propertyEntireTableInfo.getPrimaryKeyColumnNames();
		for (String ppk : ppks)
		{
			for (ImportedKeyInfo importedKeyInfo : importedKeyInfos)
			{
				if (propertyTableName.equals(importedKeyInfo.getPkTableName())
						&& ppk.equals(importedKeyInfo.getPkColumnName()))
					list1.add(importedKeyInfo);
			}
		}

		lists[0] = list0;
		lists[1] = list1;

		return lists;
	}

	/**
	 * 查找联合{@linkplain ImportedKeyInfo}，包括它自己。
	 * <p>
	 * 如果目标表是联合主键表，那么此表应该会有多个{@linkplain ImportedKeyInfo}与之对应，此方法即是查找它们。
	 * </p>
	 * 
	 * @param entireTableInfo
	 * @param importedKeyInfo
	 * @param columnInfo
	 * @return
	 */
	protected List<ImportedKeyInfo> findMyImportedKeyInfos(EntireTableInfo entireTableInfo,
			ImportedKeyInfo importedKeyInfo)
	{
		ImportedKeyInfo[] timportedKeyInfos = entireTableInfo.getImportedKeyInfos();

		List<ImportedKeyInfo> union = new ArrayList<ImportedKeyInfo>();

		for (ImportedKeyInfo timportedKeyInfo : timportedKeyInfos)
		{
			if (timportedKeyInfo == importedKeyInfo)
			{
				union.add(timportedKeyInfo);
			}
			else if (timportedKeyInfo.getFkName() != null
					&& timportedKeyInfo.getFkName().equals(importedKeyInfo.getFkName()))
			{
				union.add(timportedKeyInfo);
			}
		}

		return union;
	}

	/**
	 * 查找联合{@linkplain ExportedKeyInfo}，包括它自己。
	 * <p>
	 * 如果此表是联合主键表，那么目标表应该会有多个{@linkplain ExportedKeyInfo}与之对应，此方法即是查找它们。
	 * </p>
	 * 
	 * @param entireTableInfo
	 * @param exportedKeyInfo
	 * @return
	 */
	protected List<ExportedKeyInfo> findMyExportedKeyInfos(EntireTableInfo entireTableInfo,
			ExportedKeyInfo exportedKeyInfo)
	{
		ExportedKeyInfo[] exportedKeyInfos = entireTableInfo.getExportedKeyInfos();

		List<ExportedKeyInfo> union = new ArrayList<ExportedKeyInfo>();

		for (ExportedKeyInfo _exportedKeyInfo : exportedKeyInfos)
		{
			if (_exportedKeyInfo == exportedKeyInfo)
			{
				union.add(_exportedKeyInfo);
			}
			else if (_exportedKeyInfo.getFkName() != null
					&& _exportedKeyInfo.getFkName().equals(exportedKeyInfo.getFkName()))
			{
				union.add(_exportedKeyInfo);
			}
		}

		return union;
	}

	/**
	 * 查找{@linkplain ImportedKeyInfo}对应的{@linkplain ColumnInfo}。
	 * 
	 * @param entireTableInfo
	 * @param importedKeyInfos
	 * @return
	 */
	protected List<ColumnInfo> getColumnInfosForImportedKeyInfos(EntireTableInfo entireTableInfo,
			List<ImportedKeyInfo> importedKeyInfos)
	{
		List<ColumnInfo> columnInfos = new ArrayList<ColumnInfo>();

		for (ImportedKeyInfo importedKeyInfo : importedKeyInfos)
		{
			ColumnInfo columnInfo = getColumnInfo(entireTableInfo, importedKeyInfo.getFkColumnName());

			columnInfos.add(columnInfo);
		}

		return columnInfos;
	}

	/**
	 * 查找{@linkplain ExportedKeyInfo}对应的{@linkplain ColumnInfo}。
	 * 
	 * @param entireTableInfo
	 * @param importedKeyInfos
	 * @return
	 */
	protected List<ColumnInfo> getColumnInfosForExportedKeyInfos(EntireTableInfo entireTableInfo,
			List<ExportedKeyInfo> exportedKeyInfos)
	{
		List<ColumnInfo> columnInfos = new ArrayList<ColumnInfo>();

		for (ExportedKeyInfo exportedKeyInfo : exportedKeyInfos)
		{
			ColumnInfo columnInfo = getColumnInfo(entireTableInfo, exportedKeyInfo.getPkColumnName());

			columnInfos.add(columnInfo);
		}

		return columnInfos;
	}

	/**
	 * 获取指定名称的{@linkplain ColumnInfo}。
	 * 
	 * @param entireTableInfo
	 * @param columnName
	 * @return
	 */
	protected ColumnInfo getColumnInfo(EntireTableInfo entireTableInfo, String columnName)
	{
		ColumnInfo[] columnInfos = entireTableInfo.getColumnInfos();

		if (columnInfos == null)
			return null;

		for (ColumnInfo columnInfo : columnInfos)
		{
			if (columnInfo.getName().equals(columnName))
				return columnInfo;
		}

		return null;
	}

	/**
	 * 获取{@linkplain ImportedKeyInfo#getFkColumnName()}数组。
	 * 
	 * @param importedKeyInfos
	 * @return
	 */
	protected String[] getFkColumnNamesForImportedKeyInfo(List<ImportedKeyInfo> importedKeyInfos)
	{
		String[] columnNames = new String[importedKeyInfos.size()];

		for (int i = 0; i < importedKeyInfos.size(); i++)
			columnNames[i] = importedKeyInfos.get(i).getFkColumnName();

		return columnNames;
	}

	/**
	 * 获取{@linkplain ImportedKeyInfo#getPkColumnName()}的数组。
	 * 
	 * @param importedKeyInfos
	 * @return
	 */
	protected String[] getPkColumnNamesForImportedKeyInfo(List<ImportedKeyInfo> importedKeyInfos)
	{
		String[] columnNames = new String[importedKeyInfos.size()];

		for (int i = 0; i < importedKeyInfos.size(); i++)
			columnNames[i] = importedKeyInfos.get(i).getPkColumnName();

		return columnNames;
	}

	/**
	 * 获取{@linkplain ExportedKeyInfo#getFkColumnName()}数组。
	 * 
	 * @param exportedKeyInfos
	 * @return
	 */
	protected String[] getFkColumnNamesForExportedKeyInfo(List<ExportedKeyInfo> exportedKeyInfos)
	{
		String[] columnNames = new String[exportedKeyInfos.size()];

		for (int i = 0; i < exportedKeyInfos.size(); i++)
			columnNames[i] = exportedKeyInfos.get(i).getFkColumnName();

		return columnNames;
	}

	/**
	 * 获取{@linkplain ExportedKeyInfo#getPkColumnName()}数组。
	 * 
	 * @param exportedKeyInfos
	 * @return
	 */
	protected String[] getPkColumnNamesForExportedKeyInfo(List<ExportedKeyInfo> exportedKeyInfos)
	{
		String[] columnNames = new String[exportedKeyInfos.size()];

		for (int i = 0; i < exportedKeyInfos.size(); i++)
			columnNames[i] = exportedKeyInfos.get(i).getPkColumnName();

		return columnNames;
	}

	/**
	 * 获取指定列名的{@linkplain ImportedKeyInfo}，如果不是外键，返回{@code null}。
	 * 
	 * @param entireTableInfo
	 * @param columnName
	 * @return
	 */
	protected ImportedKeyInfo getImportedKeyInfoIf(EntireTableInfo entireTableInfo, String columnName)
	{
		ImportedKeyInfo[] importedKeyInfos = entireTableInfo.getImportedKeyInfos();

		if (importedKeyInfos == null)
			return null;

		for (ImportedKeyInfo importedKeyInfo : importedKeyInfos)
		{
			if (importedKeyInfo.getFkColumnName().equals(columnName))
				return importedKeyInfo;
		}

		return null;
	}

	/**
	 * 连接{@linkplain ImportedKeyInfo#getFkColumnName()}。
	 * 
	 * @param importedKeyInfos
	 * @return
	 */
	protected String joinFkColumnNameForImportedKeyInfo(List<ImportedKeyInfo> importedKeyInfos)
	{
		int size = importedKeyInfos.size();

		if (size == 1)
			return importedKeyInfos.get(0).getFkColumnName();
		else
		{
			StringBuilder sb = new StringBuilder();

			for (int i = 0; i < size; i++)
			{
				if (i != 0)
					sb.append("_");

				sb.append(importedKeyInfos.get(i).getFkColumnName());
			}

			return sb.toString();
		}
	}

	/**
	 * 连接{@linkplain ExportedKeyInfo#getFkColumnName()}。
	 * 
	 * @param importedKeyInfos
	 * @return
	 */
	protected String joinFkColumnNameForExportedKeyInfo(List<ExportedKeyInfo> exportedKeyInfos)
	{
		int size = exportedKeyInfos.size();

		if (size == 1)
			return exportedKeyInfos.get(0).getFkColumnName();
		else
		{
			StringBuilder sb = new StringBuilder();

			for (int i = 0; i < size; i++)
			{
				if (i != 0)
					sb.append("_");

				sb.append(exportedKeyInfos.get(i).getFkColumnName());
			}

			return sb.toString();
		}
	}

	protected ExportedFkInfo toExportedFkInfo(ExportedKeyInfo exportedKeyInfo)
	{
		return new ExportedFkInfo(exportedKeyInfo.getFkTableName(), exportedKeyInfo.getFkColumnName());
	}

	protected ExportedFkInfo toReversedExportedFkInfo(String tableName, ImportedKeyInfo importedKeyInfo)
	{
		return new ExportedFkInfo(tableName, importedKeyInfo.getFkColumnName());
	}

	protected List<ExportedFkInfo> toReversedExportedFkInfos(String tableName, List<ImportedKeyInfo> importedKeyInfos)
	{
		List<ExportedFkInfo> exportedFkInfos = new ArrayList<ExportedFkInfo>();

		for (ImportedKeyInfo importedKeyInfo : importedKeyInfos)
			exportedFkInfos.add(toReversedExportedFkInfo(tableName, importedKeyInfo));

		return exportedFkInfos;
	}

	/**
	 * 判断{@linkplain Property}的持久化特性是否已解析过。
	 * 
	 * @param property
	 * @return
	 */
	protected boolean isPersistencePropertyFeaturesResolved(Property property)
	{
		if (property == null)
			return false;

		if (property.hasFeature(PropertyKeyColumnName.class))
			return true;

		if (property.hasFeature(ModelKeyColumnName.class))
			return true;

		return false;
	}

	/**
	 * 查找双向关联目标{@linkplain Property}。
	 * 
	 * @param modelBuilder
	 * @param propertyBuilder
	 * @return
	 */
	protected Property findBidirectionalTargetProperty(ModelBuilder modelBuilder, PropertyBuilder propertyBuilder)
	{
		Model model = modelBuilder.getModel();
		Model propertyModel = propertyBuilder.getModel();

		Property[] propertyModelProperties = propertyModel.getProperties();

		if (propertyModelProperties == null)
			return null;

		for (Property propertyModelProperty : propertyModelProperties)
		{
			if (model.equals(propertyModelProperty.getModel()))
				return propertyModelProperty;
		}

		return null;
	}

	/**
	 * 判断给定的JDBC数据类型值（{@linkplain java.sql.Types}里的值）是否是基本类型值。
	 * 
	 * @param jdbcType
	 * @return
	 */
	protected boolean isPrimitiveJdbcType(int jdbcType)
	{
		boolean isPrimitive;

		switch (jdbcType)
		{
			case Types.ARRAY:
				isPrimitive = false;
				break;

			case Types.DISTINCT:
				isPrimitive = false;
				break;

			case Types.JAVA_OBJECT:
				isPrimitive = false;
				break;

			case Types.NULL:
				isPrimitive = false;
				break;

			case Types.OTHER:
				isPrimitive = false;
				break;

			case Types.REF:
				isPrimitive = false;
				break;

			case Types.STRUCT:
				isPrimitive = false;
				break;

			default:
				isPrimitive = true;
				break;
		}

		return isPrimitive;
	}

	/**
	 * 添加{@linkplain NameLabel}特性。
	 * 
	 * @param abstractFeaturedBuilder
	 * @param label
	 */
	protected void addNameLabelFeature(AbstractFeaturedBuilder abstractFeaturedBuilder, Label label)
	{
		NameLabel nameLabel = new NameLabel(label);
		abstractFeaturedBuilder.addFeature(NameLabel.class, nameLabel);
	}

	/**
	 * 添加{@linkplain DescLabel}特性。
	 * 
	 * @param abstractFeaturedBuilder
	 * @param label
	 */
	protected void addDescLabelFeature(AbstractFeaturedBuilder abstractFeaturedBuilder, Label label)
	{
		DescLabel descLabel = new DescLabel(label);
		abstractFeaturedBuilder.addFeature(DescLabel.class, descLabel);
	}

	/**
	 * 解析{@linkplain Model#getProperties()}的{@linkplain RelationMapper}特性。
	 * <p>
	 * 注意：此方法不能在{@linkplain #doResolve(Connection, ModelManager, ModelManager, String, String)}内部调用。
	 * </p>
	 * <p>
	 * 注意：要等所有{@linkplain org.datagear.persistence.features} 特性都解析设置完成后，才能执行此方法。
	 * </p>
	 * 
	 * @param model
	 */
	protected void resolveModelRelationMappers(Model model)
	{
		if (MU.isPrimitiveModel(model))
			return;
		else if (MU.isCompositeModel(model))
		{
			Property[] properties = model.getProperties();

			for (int i = 0; i < properties.length; i++)
			{
				Property property = properties[i];

				RelationMapper relationMapper = this.relationMapperResolver.resolve(model, property);

				property.setFeature(RelationMapper.class, relationMapper);
			}
		}
		else
			throw new UnsupportedOperationException();
	}

	/**
	 * 解析{@linkplain Model#getProperties()}的{@linkplain ModelKeyPropertyName}、{@linkplain PropertyKeyPropertyName}特性。
	 * <p>
	 * 注意：此方法不能在{@linkplain #doResolve(Connection, ModelManager, ModelManager, String, String)}内部调用。
	 * </p>
	 * 
	 * @param model
	 */
	protected void resolveModelAndPropertyKeyPropertyNames(Model model)
	{
		if (MU.isPrimitiveModel(model))
			return;
		else if (MU.isCompositeModel(model))
		{
			Property[] properties = model.getProperties();

			for (int i = 0; i < properties.length; i++)
			{
				Property property = properties[i];

				ModelTablePkColumnName modelTablePkColumnName = property.removeFeature(ModelTablePkColumnName.class);
				if (modelTablePkColumnName != null)
				{
					ModelKeyPropertyName modelKeyPropertyName = new ModelKeyPropertyName();

					String[] value = modelTablePkColumnName.getValue();
					if (value != null)
						modelKeyPropertyName.setValue(getPropertyNamesOfColumnNames(model, value));

					Map<Integer, String[]> mapValues = modelTablePkColumnName.getMapValues();
					if (mapValues != null)
					{
						for (Map.Entry<Integer, String[]> entry : mapValues.entrySet())
							modelKeyPropertyName.setValue(entry.getKey(),
									getPropertyNamesOfColumnNames(model, entry.getValue()));
					}

					property.setFeature(ModelKeyPropertyName.class, modelKeyPropertyName);
				}

				PropertyTablePkColumnName propertyTablePkColumnName = property
						.removeFeature(PropertyTablePkColumnName.class);
				if (propertyTablePkColumnName != null)
				{
					PropertyKeyPropertyName propertyKeyPropertyName = new PropertyKeyPropertyName();

					String[] value = propertyTablePkColumnName.getValue();
					if (value != null)
					{
						Model propertyModel = MU.getPropertyModel(property, 0);
						propertyKeyPropertyName.setValue(getPropertyNamesOfColumnNames(propertyModel, value));
					}

					Model[] propertyModels = property.getModels();
					for (int j = 0; j < propertyModels.length; j++)
					{
						String[] myValue = propertyKeyPropertyName.getOriginalValue(j);

						if (myValue != null)
							propertyKeyPropertyName.setValue(j,
									getPropertyNamesOfColumnNames(propertyModels[j], myValue));
					}

					property.setFeature(PropertyKeyPropertyName.class, propertyKeyPropertyName);
				}
			}
		}
		else
			throw new UnsupportedOperationException();
	}

	/**
	 * 获取指定列名称的{@linkplain Property}名称数组。
	 * 
	 * @param model
	 * @param columnNames
	 * @return
	 */
	protected String[] getPropertyNamesOfColumnNames(Model model, String[] columnNames)
	{
		List<String> propertyNames = new ArrayList<String>();

		for (String columnName : columnNames)
		{
			Property property = getPropertyOfColumnName(model, columnName);

			if (!propertyNames.contains(property.getName()))
				propertyNames.add(property.getName());
		}

		return propertyNames.toArray(new String[propertyNames.size()]);
	}

	/**
	 * 获取指定列名称的{@linkplain Property}。
	 * 
	 * @param model
	 * @param columnName
	 * @return
	 */
	protected Property getPropertyOfColumnName(Model model, String columnName)
	{
		Property[] properties = model.getProperties();

		if (properties != null)
		{
			for (Property property : properties)
			{
				if (isPropertyOfColumnName(model, property, columnName))
					return property;
			}
		}

		throw new DatabaseModelResolverException(
				"No Property of [" + columnName + "] column name found in [" + model + "]");
	}

	/**
	 * 判断属性是否是指定列名称。
	 * 
	 * @param model
	 * @param property
	 * @param columnName
	 * @return
	 */
	protected boolean isPropertyOfColumnName(Model model, Property property, String columnName)
	{
		ColumnName columnNameFeature = property.getFeature(ColumnName.class);

		if (columnNameFeature != null)
		{
			if (columnName.equals(columnNameFeature.getValue()))
				return true;

			Map<Integer, String> columnNameMap = columnNameFeature.getMapValues();

			if (columnNameMap != null)
			{
				for (Map.Entry<Integer, String> entry : columnNameMap.entrySet())
				{
					if (columnName.equals(entry.getValue()))
						return true;
				}
			}
		}

		PropertyKeyColumnName propertyKeyColumnNameFeature = property.getFeature(PropertyKeyColumnName.class);

		if (propertyKeyColumnNameFeature != null)
		{
			for (String myColumnName : propertyKeyColumnNameFeature.getValue())
			{
				if (columnName.equals(myColumnName))
					return true;
			}

			Map<Integer, String[]> columnNameMap = propertyKeyColumnNameFeature.getMapValues();

			if (columnNameMap != null)
			{
				for (Map.Entry<Integer, String[]> entry : columnNameMap.entrySet())
				{
					for (String myColumnName : entry.getValue())
					{
						if (columnName.equals(myColumnName))
							return true;
					}
				}
			}
		}

		MappedBy mappedByFeature = property.getFeature(MappedBy.class);

		if (mappedByFeature != null)
		{
			Model[] propertyModels = MU.getModels(property);

			for (int i = 0; i < propertyModels.length; i++)
			{
				Model propertyModel = propertyModels[i];
				Property mappedByProperty = propertyModel.getProperty(mappedByFeature.getValue(i));
				int myModelIndex = MU.getPropertyModelIndex(mappedByProperty, model);
				ModelKeyColumnName modelKeyColumnNameFeature = mappedByProperty.getFeature(ModelKeyColumnName.class);

				if (modelKeyColumnNameFeature != null)
				{
					String[] columnNames = modelKeyColumnNameFeature.getValue(myModelIndex);

					for (String myColumnName : columnNames)
					{
						if (columnName.equals(myColumnName))
							return true;
					}
				}
			}
		}

		return false;
	}

	/**
	 * 创建{@linkplain ModelBuilder}。
	 * 
	 * @return
	 */
	protected ModelBuilder createModelBuilder()
	{
		return new ModelBuilder();
	}

	/**
	 * 创建{@linkplain PropertyBuilder}。
	 * 
	 * @return
	 */
	protected PropertyBuilder createPropertyBuilder()
	{
		return new PropertyBuilder();
	}

	/**
	 * 将{@linkplain ImportedKeyRule}转换为{@linkplain RuleType}。
	 * 
	 * @param importedKeyRule
	 * @return
	 */
	protected RuleType toKeyRuleType(ImportedKeyRule importedKeyRule)
	{
		if (ImportedKeyRule.NO_ACTION.equals(importedKeyRule))
			return RuleType.NO_ACTION;
		else if (ImportedKeyRule.CASCADE.equals(importedKeyRule))
			return RuleType.CASCADE;
		else if (ImportedKeyRule.SET_NULL.equals(importedKeyRule))
			return RuleType.SET_NULL;
		else if (ImportedKeyRule.SET_DEFAUL.equals(importedKeyRule))
			return RuleType.SET_DEFAUL;
		else if (ImportedKeyRule.RESTRICT.equals(importedKeyRule))
			return RuleType.RESTRICT;
		else
			throw new UnsupportedOperationException();
	}

	/**
	 * 判断两个数组属否是排序无关相等的。
	 * 
	 * @param a
	 * @param b
	 * @return
	 */
	protected boolean equalsIgnoreOrder(String[] a, String[] b)
	{
		if (a == null && b == null)
			return true;
		else if (a == null || b == null)
			return false;
		else
		{
			Set<String> seta = new HashSet<String>();
			Set<String> setb = new HashSet<String>();

			seta.addAll(Arrays.asList(a));
			setb.addAll(Arrays.asList(b));

			return seta.equals(setb);
		}
	}

	/**
	 * {@linkplain AbstractFeatured}构建器。
	 * 
	 * @author datagear@163.com
	 *
	 */
	protected static abstract class AbstractFeaturedBuilder
	{
		protected Map<Object, Object> features;

		public AbstractFeaturedBuilder()
		{
			super();
		}

		public Map<Object, Object> getFeatures()
		{
			return features;
		}

		public void setFeatures(Map<Object, Object> features)
		{
			this.features = features;
		}

		public void addFeature(Object key, Object value)
		{
			if (this.features == null)
				this.features = new HashMap<Object, Object>();

			this.features.put(key, value);
		}

		public void addFeature(Object key)
		{
			if (this.features == null)
				this.features = new HashMap<Object, Object>();

			this.features.put(key, true);
		}
	}

	/**
	 * {@linkplain Model}构建器。
	 * 
	 * @author datagear@163.com
	 *
	 */
	protected static class ModelBuilder extends AbstractFeaturedBuilder implements PropertyNameContext
	{
		private DefaultModel model;

		private Class<?> type;

		private List<Property> properties;

		private List<Property> idProperties;

		private List<List<Property>> uniqueProperties;

		public ModelBuilder()
		{
			super();
		}

		public void init(String name)
		{
			this.model = new DefaultModel();
			this.model.setName(name);
		}

		public void build()
		{
			if (this.model == null)
				throw new IllegalStateException();

			this.model.setType(this.type);
			this.model.setProperties(getPropertyArray());
			this.model.setIdProperties(getIdPropertyArray());
			this.model.setUniqueProperties(getUniquePropertyArray());
			this.model.setFeatures(this.features);
		}

		public Model getModel()
		{
			return this.model;
		}

		public String getName()
		{
			return this.model.getName();
		}

		public Class<?> getType()
		{
			return type;
		}

		public void setType(Class<?> type)
		{
			this.type = type;
		}

		public List<Property> getProperties()
		{
			return properties;
		}

		public void setProperties(List<Property> properties)
		{
			this.properties = properties;
		}

		public List<Property> getIdProperties()
		{
			return idProperties;
		}

		public void setIdProperties(List<Property> idProperties)
		{
			this.idProperties = idProperties;
		}

		public void addProperty(Property... properties)
		{
			if (this.properties == null)
				this.properties = new ArrayList<Property>();

			for (Property property : properties)
				this.properties.add(property);
		}

		public void addIdProperty(Property... idProperties)
		{
			if (this.idProperties == null)
				this.idProperties = new ArrayList<Property>();

			for (Property property : idProperties)
				this.idProperties.add(property);
		}

		public void addUniqueProperty(int uniqueIndex, Property property)
		{
			if (this.uniqueProperties == null)
				this.uniqueProperties = new ArrayList<List<Property>>();

			while (this.uniqueProperties.size() <= uniqueIndex)
				this.uniqueProperties.add(new ArrayList<Property>());

			this.uniqueProperties.get(uniqueIndex).add(property);
		}

		public Property[] getPropertyArray()
		{
			if (this.properties == null)
				return null;

			return this.properties.toArray(new Property[this.properties.size()]);
		}

		public Property[] getIdPropertyArray()
		{
			if (this.idProperties == null)
				return null;

			return this.idProperties.toArray(new Property[this.idProperties.size()]);
		}

		public Property[][] getUniquePropertyArray()
		{
			if (this.uniqueProperties == null || this.uniqueProperties.isEmpty())
				return null;

			List<List<Property>> reUniqueProperties = new ArrayList<List<Property>>();

			for (List<Property> properties : this.uniqueProperties)
			{
				if (!properties.isEmpty())
					reUniqueProperties.add(properties);
			}

			Property[][] uniquePropertyArray = new Property[reUniqueProperties.size()][];

			for (int i = 0, len = reUniqueProperties.size(); i < len; i++)
			{
				List<Property> properties = reUniqueProperties.get(i);

				uniquePropertyArray[i] = properties.toArray(new Property[properties.size()]);
			}

			return uniquePropertyArray;
		}

		public boolean hasProperty(String propertyName)
		{
			if (this.properties == null)
				return false;

			for (Property property : this.properties)
			{
				if (property.getName().equals(propertyName))
					return true;
			}

			return false;
		}

		public boolean hasPropertyWithNameLableValue(String nameLabelValue)
		{
			if (this.properties == null)
				return false;

			for (Property property : this.properties)
			{
				NameLabel nameLabel = property.getFeature(NameLabel.class);

				if (nameLabel == null)
					continue;

				if (nameLabelValue.equals(nameLabel.getValue().getValue()))
					return true;
			}

			return false;
		}

		@Override
		public String getModelName()
		{
			return this.model.getName();
		}

		@Override
		public boolean isDuplicate(String propertyName)
		{
			return this.hasProperty(propertyName);
		}
	}

	/**
	 * {@linkplain Property}构建器。
	 * 
	 * @author datagear@163.com
	 *
	 */
	protected static class PropertyBuilder extends AbstractFeaturedBuilder
	{
		private String name;

		private Model model;

		private boolean array;

		@SuppressWarnings("rawtypes")
		private Class<? extends Collection> collectionType;

		private Object defaultValue;

		public PropertyBuilder()
		{
			super();
		}

		public String getName()
		{
			return name;
		}

		public void setName(String name)
		{
			this.name = name;
		}

		public Model getModel()
		{
			return model;
		}

		public void setModel(Model model)
		{
			this.model = model;
		}

		public boolean isArray()
		{
			return array;
		}

		public void setArray(boolean array)
		{
			this.array = array;
		}

		@SuppressWarnings("rawtypes")
		public Class<? extends Collection> getCollectionType()
		{
			return collectionType;
		}

		@SuppressWarnings("rawtypes")
		public void setCollectionType(Class<? extends Collection> collectionType)
		{
			this.collectionType = collectionType;
		}

		public boolean isCollection()
		{
			return (this.collectionType != null);
		}

		public Object getDefaultValue()
		{
			return defaultValue;
		}

		public void setDefaultValue(Object defaultValue)
		{
			this.defaultValue = defaultValue;
		}

		public Property build()
		{
			DynamicBeanProperty property = new DynamicBeanProperty();

			property.setName(this.name);
			property.setModel(this.model);
			property.setArray(this.array);
			property.setCollectionType(this.collectionType);
			property.setDefaultValue(this.defaultValue);
			property.setFeatures(this.features);

			return property;
		}
	}

	/**
	 * 导出的外键信息。
	 * 
	 * @author datagear@163.com
	 *
	 */
	protected static class ExportedFkInfo
	{
		private String fktableName;

		private String fkcolumnName;

		public ExportedFkInfo()
		{
			super();
		}

		public ExportedFkInfo(String fktableName, String fkcolumnName)
		{
			super();
			this.fktableName = fktableName;
			this.fkcolumnName = fkcolumnName;
		}

		public String getFktableName()
		{
			return fktableName;
		}

		public void setFktableName(String fktableName)
		{
			this.fktableName = fktableName;
		}

		public String getFkcolumnName()
		{
			return fkcolumnName;
		}

		public void setFkcolumnName(String fkcolumnName)
		{
			this.fkcolumnName = fkcolumnName;
		}

		@Override
		public int hashCode()
		{
			final int prime = 31;
			int result = 1;
			result = prime * result + ((fkcolumnName == null) ? 0 : fkcolumnName.hashCode());
			result = prime * result + ((fktableName == null) ? 0 : fktableName.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj)
		{
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			ExportedFkInfo other = (ExportedFkInfo) obj;
			if (fkcolumnName == null)
			{
				if (other.fkcolumnName != null)
					return false;
			}
			else if (!fkcolumnName.equals(other.fkcolumnName))
				return false;
			if (fktableName == null)
			{
				if (other.fktableName != null)
					return false;
			}
			else if (!fktableName.equals(other.fktableName))
				return false;
			return true;
		}
	}

	/**
	 * 临时Property特性-属性值表、关联表内的外键对应的模型表内的主键列名称。
	 * <p>
	 * 由于模型解析过程是嵌套调用的，可能存在未完全解析的模型，因此不能在解析过程中解析{@linkplain ModelKeyPropertyName}，需要在完全解析后才能解析。
	 * </p>
	 * 
	 * @author datagear@163.com
	 *
	 */
	protected static class ModelTablePkColumnName extends MapFeature<Integer, String[]>
			implements PropertyFeature, PersistenceFeature
	{
		public ModelTablePkColumnName()
		{
			super();
		}

		public ModelTablePkColumnName(String[] value)
		{
			super(value);
		}

		public ModelTablePkColumnName(Map<Integer, String[]> mapValues)
		{
			super(mapValues);
		}

		public ModelTablePkColumnName(String[] value, Map<Integer, String[]> mapValues)
		{
			super(value, mapValues);
		}
	}

	/**
	 * 临时Property特性-模型表、关联表内的外键对应的属性端表内的主键列名称。
	 * <p>
	 * 由于模型解析过程是嵌套调用的，可能存在未完全解析的模型，因此不能在解析过程中解析{@linkplain PropertyKeyPropertyName}，需要在完全解析后才能解析。
	 * </p>
	 * 
	 * @author datagear@163.com
	 *
	 */
	protected static class PropertyTablePkColumnName extends MapFeature<Integer, String[]>
			implements PropertyFeature, PersistenceFeature
	{
		public PropertyTablePkColumnName()
		{
			super();
		}

		public PropertyTablePkColumnName(String[] value)
		{
			super(value);
		}

		public PropertyTablePkColumnName(Map<Integer, String[]> mapValues)
		{
			super(mapValues);
		}

		public PropertyTablePkColumnName(String[] value, Map<Integer, String[]> mapValues)
		{
			super(value, mapValues);
		}
	}

	/**
	 * 对于某些{@linkplain ColumnInfo}无法解析为{@linkplain Model}情况（比如列类型无法识别）的处理方式。
	 * 
	 * @author datagear@163.com
	 *
	 */
	public static enum UnresolvableColumnHandleWay
	{
		/** 抛出异常，终止解析 */
		THROW,

		/** 使用占位符模型，继续解析其他 */
		PLACEHOLDER,

		/** 忽略无法解析的列，继续解析其他 */
		IGNORE
	}
}
